﻿//-------------------------------------------------------------------------------
// <copyright file="FBAuthorizationBrowser.cs" company="TaskyMedia LLC">
//     Copyright (c) TaskyMedia LLC.
// </copyright>
//-------------------------------------------------------------------------------

/*
 * Copyright (c) 2010, TaskyMedia LLC
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 *   Redistributions of source code must retain the above copyright notice, this 
 *     list of conditions and the following disclaimer.
 *   Redistributions in binary form must reproduce the above copyright notice, this
 *     list of conditions and the following disclaimer in the documentation and/or
 *     other materials provided with the distribution.
 *   Neither the name of TaskyMedia LLC nor the names of its contributors may be used
 *   to endorse or promote products derived from this software without specific prior
 *   written permission.
 *   
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGE. 
 */

namespace FBGraphApi.Desktop
{
    using System;
    using System.Globalization;
    using System.Text.RegularExpressions;
    using System.Windows.Forms;    

    /// <summary>
    /// Provides an embedded WebBrowser window to automatically handle Graph Application
    /// authorization and callbacks.
    /// </summary>
    public partial class FBAuthorizationBrowser : UserControl
    {
        /// <summary>
        /// OAuth desktop authorization URI.
        /// </summary>
        private const string AUTHORIZE_URL = @"https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri=http://www.facebook.com/connect/login_success.html&type=user_agent&display=popup";

        /// <summary>
        /// OAuth desktop authorization URI (with permissions).
        /// </summary>
        private const string AUTHORIZE_URL_W_PERMISSIONS = @"https://graph.facebook.com/oauth/authorize?client_id={0}&redirect_uri=http://www.facebook.com/connect/login_success.html&type=user_agent&display=popup&scope={1}";

        /// <summary>
        /// Regular expression to validate that the Client Id is in the correct format.
        /// </summary>
        private const string CLIENT_ID_VALIDATION_EXPRESSION = @"^\d+$";

        /// <summary>
        /// Regular expression to detect the failure redirect URI.
        /// </summary>
        private const string FAILURE_EXPRESSION = @"^http://www.facebook.com/connect/login_success.html?error_reason=";

        /// <summary>
        /// Regular expression to detect the success redirect URI and retrieve
        /// parameters from the query string.
        /// </summary>
        private const string SUCCESS_EXPRESSION = @"^http://www.facebook.com/connect/login_success.html#access_token=(\S+)&expires_in=(\d+)$";

        /// <summary>
        /// The Facebook application client Id.
        /// </summary>
        private string _clientId;

        /// <summary>
        /// The extended permissions requested by the Application.
        /// </summary>
        private ExtendedPermission[] _requestedPermissions;

        /// <summary>
        /// Initializes a new instance of the <see cref="FBAuthorizationBrowser"/> class with
        /// the settings in the App.config file.
        /// </summary>
        public FBAuthorizationBrowser()
            : this(FBGraphApi.FBSettings.ClientId, FBGraphApi.FBSettings.RequestedPermissions.ToArray())
        {            
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="FBAuthorizationBrowser"/> class with
        /// the specified Facebook client Id and requested extended permissions.
        /// </summary>
        /// <param name="clientId">The client id.</param>
        /// <param name="requestedPermissions">The requested permissions.</param>
        public FBAuthorizationBrowser(string clientId, params ExtendedPermission[] requestedPermissions)
        {
            this._clientId = clientId;
            this._requestedPermissions = requestedPermissions;
            this.InitializeComponent();
        }

        /// <summary>
        /// Occurs when the Application was successfully authorized by the user.
        /// </summary>
        public event EventHandler<FBAuthorizationSuccessfulEventArgs> AuthorizationSuccessful;

        /// <summary>
        /// Occurs when Application authorization failed.
        /// </summary>
        public event EventHandler<EventArgs> AuthorizationFailed;

        /// <summary>
        /// Starts the authorization process.
        /// </summary>
        public void StartAuthorize()
        {
            // Verify the Client Id.
            if (!Regex.IsMatch(this._clientId, CLIENT_ID_VALIDATION_EXPRESSION))
            {
                throw new InvalidOperationException("Client Id must be numeric");
            }

            string authorizeUriString = String.Empty;

            if (this._requestedPermissions == null)
            {
                authorizeUriString = String.Format(CultureInfo.InvariantCulture, AUTHORIZE_URL, this._clientId);
            }
            else
            {
                authorizeUriString = String.Format(CultureInfo.InvariantCulture, AUTHORIZE_URL_W_PERMISSIONS, this._clientId, ExtendedPermissions.GetPermissionsQueryString(this._requestedPermissions));
            }

            Uri authorizeUri = new Uri(authorizeUriString, UriKind.Absolute);
            this.FacebookWebBrowser.Navigate(authorizeUri);
        }

        /// <summary>
        /// Handles the DocumentCompleted event of the FacebookWebBrowser control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Forms.WebBrowserDocumentCompletedEventArgs"/> instance containing the event data.</param>
        private void FacebookWebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            if (e.Url.AbsolutePath == "/connect/login_success.html")
            {
                if (Regex.IsMatch(e.Url.AbsoluteUri, FAILURE_EXPRESSION))
                {
                    this.OnAuthorizationFailed();
                }
                else
                {
                    MatchCollection matches = Regex.Matches(e.Url.AbsoluteUri, SUCCESS_EXPRESSION);
                    if (matches.Count > 0)
                    {
                        string accessToken = matches[0].Groups[1].Value;
                        DateTime expiration = DateTime.Now.AddSeconds(int.Parse(matches[0].Groups[2].Value, CultureInfo.InvariantCulture));
                        FBAuthorizationSuccessfulEventArgs args = new FBAuthorizationSuccessfulEventArgs(accessToken, expiration);
                        this.OnAuthorizationSuccessful(args);
                    }
                    else
                    {
                        this.OnAuthorizationFailed();
                    }
                }
            }
        }

        /// <summary>
        /// Raises the <see cref="E:AuthorizationSuccessful"/> event.
        /// </summary>
        /// <param name="e">The <see cref="FBGraphApi.Desktop.FBAuthorizationSuccessfulEventArgs"/> instance containing the event data.</param>
        private void OnAuthorizationSuccessful(FBAuthorizationSuccessfulEventArgs e)
        {
            if (this.AuthorizationSuccessful != null)
            {
                this.AuthorizationSuccessful(this, e);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:AuthorizationFailed"/> event.
        /// </summary>
        private void OnAuthorizationFailed()
        {
            if (this.AuthorizationFailed != null)
            {
                this.AuthorizationFailed(this, EventArgs.Empty);
            }
        }
    }
}
